#include "StdAfx.h"
#include "AudioEngine.h"
#include <windows.h>   
#include <stdio.h>   
#include <mmsystem.h>   
#pragma comment(lib, "winmm.lib") 

const int KWaveOutBufferReadySize=1024*50;

AudioEngine::AudioEngine(AudioEngineInterface* audioEngineInterface)
{
	m_audioEngineInterface=audioEngineInterface;
	m_hWaveIn=0;
	m_waveInBuffer=new MutableBuffer();
	m_isRecoding=false;

	m_hWaveOut=0;
	m_isPlaying=false;
	m_threadId=0;
	m_hSingleObjectEvent=0;
	m_hWaveInSingleObjectEvent=0;

	m_waveOutTempBuffer=new MutableBuffer();

	m_waveOutHdr.lpData = NULL;
	m_waveOutHdr.dwBufferLength = 0; 
	m_waveOutHdr.dwFlags = 0;  
	m_waveOutHdr.dwLoops = 0;  
	m_waveOutHdr.dwBytesRecorded=0;
}

AudioEngine::~AudioEngine(void)
{
	bool waitThreadEnd= m_threadId!=0;

	stopPlay();
	stopRecord();

	if(waitThreadEnd)
	{
		Sleep(500);
		//ExitThread()
	}

	delete m_waveInBuffer;
	delete m_waveOutTempBuffer;
	m_waveInBuffer=NULL;
	m_waveOutTempBuffer=NULL;

	m_audioEngineInterface=NULL;

	while(!m_waveOutBufferQueue.empty())
	{
		delete m_waveOutBufferQueue.front();
		m_waveOutBufferQueue.pop();
	}
}

bool AudioEngine::startRecord(bool recodingPlaying)
{
	/**************************1.Record*************************************/
	//waveInOpen -> waveInPrepareHeader -> waveInAddBuffer 
	//	         -> waveInStart -> waveInStop -> waveInReset ->waveInUnprepareHeader -> waveInClose

	//clear
	m_waveInBuffer->clear();

	//Device   
	int nReturn = waveInGetNumDevs();  
	if(nReturn<=0)
	{
		return false;
	}

	printf("input device count : %d\n", nReturn);  
	for (int i=0; i<nReturn; i++)  
	{  
		WAVEINCAPS wic;  
		waveInGetDevCaps(i, &wic, sizeof(WAVEINCAPS));  
		printf("#%d\tdevice name : %s\n", i, wic.szPname);  
	}  

	//open   
	m_waveInFormateX.wFormatTag = WAVE_FORMAT_PCM;  
	m_waveInFormateX.nChannels = 2;  
	m_waveInFormateX.nSamplesPerSec = 44100;  
	m_waveInFormateX.wBitsPerSample = 16; 
	m_waveInFormateX.nBlockAlign = m_waveInFormateX.wBitsPerSample*m_waveInFormateX.nChannels/8;
	m_waveInFormateX.nAvgBytesPerSec = m_waveInFormateX.nSamplesPerSec*m_waveInFormateX.nBlockAlign;  
	m_waveInFormateX.cbSize = 0;  
	MMRESULT ret= waveInOpen(&m_hWaveIn, WAVE_MAPPER, &m_waveInFormateX, (DWORD_PTR)AudioEngine::waveInProc, (DWORD_PTR)this, CALLBACK_FUNCTION);  
	if(ret!=0)
	{
		return false;
	}

	WAVEINCAPS wic;  
	waveInGetDevCaps((UINT_PTR)m_hWaveIn, &wic, sizeof(WAVEINCAPS));  
	printf("opened input device : %s\n", wic.szPname);  

	// prepare buffer    
	for (int i=0; i<FRAGMENT_NUM; i++)  
	{  
		m_waveInHdrArray[i].lpData = new char[FRAGMENT_SIZE];  
		m_waveInHdrArray[i].dwBufferLength = FRAGMENT_SIZE;  
		m_waveInHdrArray[i].dwBytesRecorded = 0;  
		m_waveInHdrArray[i].dwUser = NULL;  
		m_waveInHdrArray[i].dwFlags = 0;  
		m_waveInHdrArray[i].dwLoops = 1;  
		m_waveInHdrArray[i].lpNext = NULL;  
		m_waveInHdrArray[i].reserved = 0;  

		waveInPrepareHeader(m_hWaveIn, &m_waveInHdrArray[i], sizeof(WAVEHDR));  
		waveInAddBuffer(m_hWaveIn, &m_waveInHdrArray[i], sizeof(WAVEHDR));  
	}  

	//start record   
	m_isRecoding=true;
	ret=waveInStart(m_hWaveIn);   

	m_hWaveInSingleObjectEvent=CreateEvent(NULL,FALSE,FALSE,NULL);

	if(recodingPlaying)
	{
		m_waveOutFormateX=m_waveInFormateX;
		startPlay();
	}

	return true;
}

void AudioEngine::CallRun(LPVOID p)
{
	AudioEngine* pAudioEngine=(AudioEngine*)p;

	WaitForSingleObject(pAudioEngine->m_hSingleObjectEvent,INFINITE);

	if(pAudioEngine->m_hSingleObjectEvent!=0)
	{
		CloseHandle(pAudioEngine->m_hSingleObjectEvent);
		pAudioEngine->m_hSingleObjectEvent=0;
	}

	pAudioEngine->stopWaveOut();
}

//record callback function   
void CALLBACK AudioEngine::waveInProc(HWAVEIN hwi,        
	UINT uMsg,           
	DWORD_PTR dwInstance,    
	DWORD_PTR dwParam1,    
	DWORD_PTR dwParam2     )  
{  
	AudioEngine* audioEngine=(AudioEngine*)dwInstance;

	if (WIM_DATA==uMsg)  
	{  
		if(!audioEngine->m_isRecoding)
		{
			SetEvent(audioEngine->m_hWaveInSingleObjectEvent);
			return;
		}

		LPWAVEHDR pwh = (LPWAVEHDR)dwParam1;  

		unsigned long size = pwh->dwBytesRecorded;
		audioEngine->addNextWaveInBuffer(pwh->lpData, size);

		//TRACE("receive size:%d\n",size);

		waveInUnprepareHeader(hwi, pwh, sizeof(WAVEHDR));  
		waveInPrepareHeader(hwi, pwh, sizeof(WAVEHDR));
		waveInAddBuffer(hwi, pwh, sizeof(WAVEHDR)); 
	}  
	else if (WIM_OPEN==uMsg)  
	{
		printf("WIM_OPEN");
	}
	else if (WIM_CLOSE==uMsg)  
	{
		printf("WIM_CLOSE");
	}
}  
void AudioEngine::addNextWaveInBuffer(const void* newBuffer,int newBufferSize)
{
	m_waveInBuffer->addBuffer(newBuffer, newBufferSize);

	if(m_audioEngineInterface)
	{
		m_audioEngineInterface->onAudioRecordedBuffer(newBuffer, newBufferSize,m_waveInFormateX);
	}

	if(m_isPlaying)
	{
		addWaveOutBuffer(newBuffer,newBufferSize);
	}
}
MutableBuffer* AudioEngine::waveInBuffer() const
{
	return m_waveInBuffer;
}
const WAVEFORMATEX& AudioEngine::waveInFormateX() const
{
	return m_waveInFormateX;
}
bool AudioEngine::getWaveHeadedBuffer(const WAVEFORMATEX& wavform,const void* waveDataBuffer,unsigned int waveDataBufferSize,MutableBuffer* waveHeadedBuffer)
{
	if(!waveDataBuffer || waveDataBufferSize<=0)
	{
		return false;
	}

	//8 bytes
	waveHeadedBuffer->addBuffer("RIFF",4);
	unsigned int nValue=36+waveDataBufferSize;
	waveHeadedBuffer->addBuffer(&nValue,4);

	//36 bytes
	waveHeadedBuffer->addBuffer("WAVEfmt ",8);
	nValue=16;
	waveHeadedBuffer->addBuffer(&nValue,4);
	waveHeadedBuffer->addBuffer(&wavform,nValue);
	waveHeadedBuffer->addBuffer("data",4);
	nValue=waveDataBufferSize;
	waveHeadedBuffer->addBuffer(&nValue,4);

	//waveDataBufferSize bytes
	waveHeadedBuffer->addBuffer(waveDataBuffer,waveDataBufferSize);

	return true;
}

bool AudioEngine::getWaveBuffer(WAVEFORMATEX& wavform,MutableBuffer* waveDataBuffer,const void* waveHeadedBuffer,unsigned int waveHeadedBufferSize)
{
	if(!waveHeadedBuffer || waveHeadedBufferSize<=0)
	{
		return false;
	}

	char* pWaveHeadedBuffer=(char*)waveHeadedBuffer;

	if(strncmp((char*)pWaveHeadedBuffer,"RIFF",4)!=0)
	{
		return false;
	}
	pWaveHeadedBuffer += 4;

	int nValue = *((int*)pWaveHeadedBuffer);
	if(nValue != (waveHeadedBufferSize-8))
	{
		return false;
	}
	pWaveHeadedBuffer += 4;

	if(strncmp((char*)pWaveHeadedBuffer,"WAVEfmt ",8)!=0)
	{
		return false;
	}
	pWaveHeadedBuffer += 8;

	nValue = *((int*)pWaveHeadedBuffer);
	pWaveHeadedBuffer += 4;

	memcpy(&wavform,pWaveHeadedBuffer,nValue);
	wavform.cbSize=0;
	pWaveHeadedBuffer += nValue;

	if(strncmp((char*)pWaveHeadedBuffer,"data",4)==0)
	{
		pWaveHeadedBuffer += 4;

		int waveDataBufferSize = *((int*)pWaveHeadedBuffer);
		pWaveHeadedBuffer += 4;

		waveDataBuffer->addBuffer(pWaveHeadedBuffer,waveDataBufferSize); 
	}
	else if(strncmp((char*)pWaveHeadedBuffer,"FLLR",4)==0)
	{
		pWaveHeadedBuffer += 4;

		int byteOffset=pWaveHeadedBuffer-waveHeadedBuffer;
		waveDataBuffer->addBuffer(pWaveHeadedBuffer,waveHeadedBufferSize-byteOffset);
	}
	else
	{
		return false;
	}
	
	return true;
}

void AudioEngine::stopRecord()
{
	// clean   
	if(m_isRecoding)
	{
		m_isRecoding=false;

		WaitForSingleObject(m_hWaveInSingleObjectEvent,INFINITE);
		CloseHandle(m_hWaveInSingleObjectEvent);
		m_hWaveInSingleObjectEvent=0;

		waveInStop(m_hWaveIn); //wait until cache full ?
		waveInReset(m_hWaveIn);

		for (int i=0; i<FRAGMENT_NUM; i++)  
		{  
			waveInUnprepareHeader(m_hWaveIn, &m_waveInHdrArray[i], sizeof(WAVEHDR));  
			delete[] m_waveInHdrArray[i].lpData;  
		} 
		waveInClose(m_hWaveIn); 

		if(m_isPlaying)
		{
			stopPlay();
		}
	}
}

bool AudioEngine::startPlay()
{
	/**************************2.play**************************/  
	//waveOutOpen -> waveOutPrepareHeader -> waveOutWrite -> waveOutReset -> waveOutUnprepareHeader -> waveOutClose

	// Device   
	int nReturn = waveOutGetNumDevs();  
	if(nReturn<=0)
	{
		return false;
	}

	printf("\noutput device count : %d\n", nReturn);  
	for (int i=0; i<nReturn; i++)  
	{  
		WAVEOUTCAPS woc;  
		waveOutGetDevCaps(i, &woc, sizeof(WAVEOUTCAPS));  
		printf("#%d\tdevice name : %s\n", i, woc.szPname);  
	}  

	// open   
	MMRESULT mmRESULT=waveOutOpen(&m_hWaveOut, WAVE_MAPPER, &m_waveOutFormateX, (DWORD_PTR)AudioEngine::waveOutProc, (DWORD_PTR)this, CALLBACK_FUNCTION);  
	if(MMSYSERR_NOERROR!=mmRESULT)
	{
		return false;
	}

	WAVEOUTCAPS woc;  
	waveOutGetDevCaps((UINT_PTR)m_hWaveOut, &woc, sizeof(WAVEOUTCAPS));  
	printf("opened output device : %s\n", woc.szPname);  

	playNextBuffer();

	m_isPlaying=true;

	m_hSingleObjectEvent=CreateEvent(NULL,FALSE,FALSE,NULL); 

	HANDLE hThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)CallRun,this,0,&m_threadId);
	CloseHandle(hThread);  

	return true;
}

void CALLBACK AudioEngine::waveOutProc(  HWAVEOUT hwo,     
	UINT uMsg,           
	DWORD_PTR dwInstance,     
	DWORD_PTR dwParam1,      
	DWORD_PTR dwParam2     )  
{  
	if (WOM_DONE == uMsg)  
	{  
		AudioEngine* audioEngine=(AudioEngine*)dwInstance;

		LPWAVEHDR pwh = (LPWAVEHDR)dwParam1;  
		pwh->dwBytesRecorded = 0; 

		if(audioEngine->m_isPlaying && !audioEngine->m_isRecoding)
		{
			//play file finished,send a auto finished event.
			SetEvent(audioEngine->m_hSingleObjectEvent);

			if(audioEngine->m_audioEngineInterface)
			{
				audioEngine->m_audioEngineInterface->onAudioPlayFinished();
			}
		}
	}  
	else if (WOM_OPEN == uMsg)  
	{
		printf("WOM_OPEN");
	}
	else if (WOM_CLOSE == uMsg)  
	{
		printf("WOM_CLOSE");
	}
}  

void AudioEngine::playNextBuffer()
{
	if(!m_waveOutBufferQueue.empty())
	{
		if(m_waveOutHdr.dwBytesRecorded==0)
		{
			if(!m_waveOutBufferQueue.empty())
			{
				MutableBuffer* buffer=m_waveOutBufferQueue.front();

				// prepare buffer  
				waveOutUnprepareHeader(m_hWaveOut, &m_waveOutHdr, sizeof(WAVEHDR)); 
				delete[] m_waveOutHdr.lpData;
				m_waveOutHdr.lpData = (LPSTR)buffer->allocBuffer();  
				m_waveOutHdr.dwBufferLength = buffer->bufferSize();  
				m_waveOutHdr.dwFlags = 0;  
				m_waveOutHdr.dwLoops = 0;  
				m_waveOutHdr.dwBytesRecorded = 1;  //used as flag filed in wave out 
				waveOutPrepareHeader(m_hWaveOut, &m_waveOutHdr, sizeof(WAVEHDR));  
				// play   
				waveOutWrite(m_hWaveOut, &m_waveOutHdr, sizeof(WAVEHDR));

				delete m_waveOutBufferQueue.front();
				m_waveOutBufferQueue.pop();
			}
		}  
	}
}

void AudioEngine::stopPlay()
{
	if(m_threadId!=0)
	{
		m_threadId=0;

		SetEvent(m_hSingleObjectEvent);
	}
}

void AudioEngine::stopWaveOut()
{
	if(m_hWaveOut!=0)
	{
		m_isPlaying=false;
		waveOutReset(m_hWaveOut);  
   
		waveOutUnprepareHeader(m_hWaveOut, &m_waveOutHdr, sizeof(WAVEHDR)); 
		delete[] m_waveOutHdr.lpData;
		m_waveOutHdr.lpData=NULL;
		m_waveOutHdr.dwBufferLength=0;
		m_waveOutHdr.dwFlags=0;
		m_waveOutHdr.dwLoops=0; 
		m_waveOutHdr.dwBytesRecorded=0; 

		waveOutClose(m_hWaveOut); 
		m_hWaveOut=0;

		while(!m_waveOutBufferQueue.empty())
		{
			delete m_waveOutBufferQueue.front();
			m_waveOutBufferQueue.pop();
		}

		if(m_waveOutTempBuffer)
		{
			m_waveOutTempBuffer->clear();
		}
	}
}

void AudioEngine::setWaveOutVolumePercent(float percent)
{
	DWORD leftVolume=0xFFFF*percent;
	DWORD rightVolume=leftVolume<<16;
	waveOutSetVolume(m_hWaveOut,leftVolume+rightVolume);
}

void AudioEngine::setWaveOutFormateX(WAVEFORMATEX waveOutFormateX)
{
	m_waveOutFormateX=waveOutFormateX;
}

void AudioEngine::addWaveOutBuffer(const MutableBuffer* buffer)
{
	addWaveOutBuffer(buffer->buffer(),buffer->bufferSize());
}

void AudioEngine::addWaveOutBuffer(const void* newBuffer,int newBufferSize)
{
	m_waveOutTempBuffer->addBuffer(newBuffer,newBufferSize);

	if(m_waveOutTempBuffer->bufferSize()>=(m_isRecoding?KWaveOutBufferReadySize:1))
	{
		MutableBuffer* pBuffer=new MutableBuffer();
		pBuffer->setBuffer(m_waveOutTempBuffer->buffer(),m_waveOutTempBuffer->bufferSize());
		m_waveOutBufferQueue.push(pBuffer);
		m_waveOutTempBuffer->clear();

		if(m_isPlaying)
		{
			playNextBuffer();
		}
	}
}

void AudioEngine::setAudioEngineInterface(AudioEngineInterface* audioEngineInterface)
{
	m_audioEngineInterface=audioEngineInterface;
}

AudioEngineInterface* AudioEngine::audioEngineInterface() const
{
	return m_audioEngineInterface;
}